﻿using System;
using System.Text;
using System.Reflection;
using System.Collections;

using Boo.Lang.Compiler;
using Boo.Lang.Compiler.IO;
using Boo.Lang.Compiler.Pipelines;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;

namespace Emptiness.Scripting
{

    public class BooScriptManager
    {
        const string SCRIPT_FOLDER = "Scripting\\Scripts\\";
        const string BOO_EXTENSION = ".boo";
        public static Dictionary<string, MethodInfo> coreFunctions = null;
        public static Dictionary<string, Type> booScripts = null;

        public BooScriptManager()
        {
            booScripts = new Dictionary<string, Type>();
            coreFunctions = new Dictionary<string, MethodInfo>();

            foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
            {
                bool loadClass = false;
                foreach (var attr in type.GetCustomAttributes(typeof(BooAttribute), false))
                {
                    if (attr.GetType() == typeof(BooAttribute))
                    {
                        loadClass = true;
                        break;
                    }
                }
                if (loadClass)
                {
                    object obj = Activator.CreateInstance(type);
                    RegisterBooFunctions(obj);
                }
                
            }
            
        }

        public void ExecuteScript(string module, string method, List<Object>objectToLoad, bool reloadModule)
        {
            if (!booScripts.ContainsKey(module) || reloadModule)
            {
                if (!LoadScript(module, module))
                {
                    return;
                }
            }
            MethodInfo methodInfo;
            if (objectToLoad != null && objectToLoad.Count > 0)
            {
                methodInfo = booScripts[module].GetMethod("loadModules");
                foreach (var obj in objectToLoad)
                {
                    methodInfo.Invoke(null, new object[] {obj.GetType().Name, obj });
                }
                
            }
            try
            {
                methodInfo = booScripts[module].GetMethod(method);

                methodInfo.Invoke(null, null);
            }
            catch (Exception e)
            {
                EventLog.WriteEntry("Emptiness", e.ToString());
            }
            
        }


        public bool LoadScript(string script, string module)
        {
            bool loaded = false;
            script = SCRIPT_FOLDER + script + BOO_EXTENSION;
            if (File.Exists(script))
            {
                BooCompiler compiler = new BooCompiler();
                compiler.Parameters.Input.Add(new FileInput(script));
                compiler.Parameters.Pipeline = new CompileToMemory();
                compiler.Parameters.Ducky = true;

                CompilerContext context = compiler.Run();
                //Note that the following code might throw an error if the Boo script had bugs.
                //Poke context.Errors to make sure.
                if (context.GeneratedAssembly != null)
                {
                    try
                    {
                        Type scriptModule = context.GeneratedAssembly.GetType(module);
                        MethodInfo load = scriptModule.GetMethod("loadModules");
                        foreach (var info in coreFunctions)
                        {
                            load.Invoke(null, new object[] {info.Key, info.Value });
                        }
                        if (!booScripts.ContainsKey(module))
                            booScripts.Add(module, scriptModule);
                        else
                            booScripts[module] = scriptModule;

                        loaded = true;
                    }
                    catch (Exception e)
                    {
                        EventLog.WriteEntry("Emptiness", e.ToString());
                        loaded = false;
                    }
                }
                else
                {
                    foreach (CompilerError error in context.Errors)
                        EventLog.WriteEntry("Emptiness", error.ToString());
                }
            }

            return loaded;
        }

        private CompilerContext CompileBoo(string script)
        {
            CompilerContext context = null;
            if (File.Exists(SCRIPT_FOLDER + script))
            {
                try
                {
                    BooCompiler compiler = new BooCompiler();
                    compiler.Parameters.Input.Add(new FileInput(script));
                    compiler.Parameters.Pipeline = new CompileToMemory();
                    compiler.Parameters.Ducky = true;

                    context = compiler.Run();
                }
                catch (Exception e)
                {
                    EventLog.WriteEntry("Emptiness", e.ToString());
                }
                
            }
            return context;
        }

        public void LoadScriptFunctions(string script, string module)
        {
            var context = CompileBoo(script);
            if (context != null)
            {
                if (context.GeneratedAssembly != null)
                {
                    try 
                    {
                        Type scriptModule = context.GeneratedAssembly.GetType(module);
                        MethodInfo load = scriptModule.GetMethod("loadModules");
                        foreach (var info in coreFunctions)
                        {
                            load.Invoke(null, new object[] { info.Key, info.Value });
                        }
                        booScripts.Add(module, scriptModule);
                    }
                    catch (Exception e)
                    {
                        EventLog.WriteEntry("Emptiness", e.ToString());
                    }
                   
                }
                else
                {
                    foreach (CompilerError error in context.Errors)
                        EventLog.WriteEntry("Emptiness", error.ToString());
                }
            }
        }

        private void RegisterBooFunctions(Object pTarget)
        {
            // Sanity checks
            if (coreFunctions == null)
                return;

            // Get the target type
            Type pTrgType = pTarget.GetType();

            // ... and simply iterate through all it's methods
            foreach (MethodInfo mInfo in pTrgType.GetMethods())
            {
                // ... then through all this method's attributes
                foreach (Attribute attr in Attribute.GetCustomAttributes(mInfo))
                {
                    // and if they happen to be one of our AttrLuaFunc attributes
                    if (attr.GetType() == typeof(BooAttribute))
                    {
                        BooAttribute pAttr = (BooAttribute)attr;
                        Hashtable pParams = new Hashtable();

                        // Get the desired function name and doc string, along with parameter info
                        String strFName = pAttr.getFuncName();
                        String strFDoc = pAttr.getFuncDoc();
                        String[] pPrmDocs = pAttr.getFuncParams();

                        // Now get the expected parameters from the MethodInfo object
                        ParameterInfo[] pPrmInfo = mInfo.GetParameters();

                        // If they don't match, someone forgot to add some documentation to the 
                        // attribute, complain and go to the next method
                        if (pPrmDocs != null && (pPrmInfo.Length != pPrmDocs.Length))
                        {
                            Console.WriteLine("Function " + mInfo.Name + " (exported as " +
                                              strFName + ") argument number mismatch. Declared " +
                                              pPrmDocs.Length + " but requires " +
                                              pPrmInfo.Length + ".");
                            break;
                        }

                        // Build a parameter <-> parameter doc hashtable
                        for (int i = 0; i < pPrmInfo.Length; i++)
                        {
                            pParams.Add(pPrmInfo[i].Name, pPrmDocs[i]);
                        }

                        // Get a new function descriptor from this information
                        BooFunctionDescriptor pDesc = new BooFunctionDescriptor(strFName, strFDoc, pParams, new ArrayList());

                        // Add it to the global hashtable
                        coreFunctions.Add(strFName, mInfo);
                    }
                }
            }
        }
    }
}